﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Transactions;
using DomainDriver.DomainModeling.DomainModels;
using DomainDriver.DomainModeling.Exceptions;

namespace DomainDriver.DomainModeling.TransactionScripts
{
    public abstract class TransactionScriptBase : ITransactionScript
    {
        private TransactionScopeOption m_TransactionScopeOption;
        private Nullable<TimeSpan> m_TransactionTimeout;
        private TransactionScope m_TransactionScope;
        private IDomainModel m_DomainModel;
        private bool m_HasRun;
        private Nullable<bool> m_WasSuccessful;
        private bool m_OpenedDb = false;

        public TransactionScriptBase()
        {
            m_TransactionScopeOption = TransactionScopeOption.Required;
            m_TransactionTimeout = null;
            m_TransactionScope = null;
            m_DomainModel = null;
            m_HasRun = false;
            m_WasSuccessful = null;
        }

        public TransactionScopeOption TransactionScopeOption
        {
            get { return m_TransactionScopeOption; }
        }

        public Nullable<TimeSpan> TransactionTimeout
        {
            get { return m_TransactionTimeout; }
        }

        public IDomainModel DomainModel
        {
            get { return m_DomainModel; }
            set
            {
                if (m_DomainModel != null)
                { throw new ApplicationException("DomainModel can only be set once."); }
                m_DomainModel = value;
            }
        }

        public virtual bool IsReady()
        {
            return true;
        }

        protected abstract void RunScriptLogic();

        public bool HasRun
        {
            get { return m_HasRun; }
        }

        public bool WasSuccessful
        {
            get
            {
                if (!HasRun)
                { throw new TransactionScriptFailureException("The TransactionScript has not run yet."); }
                return m_WasSuccessful.Value;
            }
        }

        public void CompleteTransaction()
        {
            if (m_TransactionScope == null)
            { return; }

            m_TransactionScope.Complete();
            m_TransactionScope.Dispose();
            m_TransactionScope = null;
        }

        public void Run()
        {
            if (HasRun)
            { throw new TransactionScriptFailureException("The TransactionScript has already run."); }
            if (!IsReady())
            { throw new TransactionScriptFailureException("The TransactionScript is not ready to be run."); }

            m_WasSuccessful = false;
            m_HasRun = true;

            m_TransactionScope = null;
            try
            {
                if (!TransactionTimeout.HasValue)
                { m_TransactionScope = new TransactionScope(TransactionScopeOption, TimeSpan.Zero); }
                else
                { m_TransactionScope = new TransactionScope(TransactionScopeOption, TransactionTimeout.Value); }

                if (m_DomainModel.DataProvider.DataSource != null)
                {
                    if (m_DomainModel.DataProvider.DataSource is DbContext)
                    {
                        DbContext dbContext = m_DomainModel.DataProvider.DataSource as DbContext;
                        if (dbContext.Database.Exists())
                        {
                            if (dbContext.Database.Connection.State == ConnectionState.Closed)
                            {
                                m_OpenedDb = true;
                                dbContext.Database.Connection.Open();
                            }
                        }
                    }
                }

                RunScriptLogic();

                if (m_TransactionScope != null)
                { m_TransactionScope.Complete(); }
                m_WasSuccessful = true;
            }
            catch (Exception exception)
            {
                throw new TransactionScriptFailureException("TransactionScript failed to complete successfully.", exception);
            }
            finally
            {
                if (m_TransactionScope != null)
                { m_TransactionScope.Dispose(); }
                m_TransactionScope = null;

                if (m_OpenedDb)
                {
                    DbContext dbContext = m_DomainModel.DataProvider.DataSource as DbContext;
                    if (dbContext.Database.Exists())
                    {
                        if (dbContext.Database.Connection.State == ConnectionState.Open)
                        { dbContext.Database.Connection.Close(); }
                    }
                }
            }
        }
    }

    public abstract class TransactionScriptBase<RETURN_TYPE> : TransactionScriptBase, ITransactionScript<RETURN_TYPE>
    {
        private RETURN_TYPE m_Result;

        public TransactionScriptBase()
        {
            m_Result = default(RETURN_TYPE);
        }

        protected abstract RETURN_TYPE RunScriptLogicWithResult();

        public RETURN_TYPE Result
        {
            get
            {
                if (!HasRun)
                { throw new TransactionScriptFailureException("The TransactionScript has not run yet."); }
                if (!WasSuccessful)
                { throw new TransactionScriptFailureException("The TransactionScript did not complete successfully."); }
                return m_Result;
            }
        }

        protected override void RunScriptLogic()
        {
            m_Result = RunScriptLogicWithResult();
        }
    }
}